package org.ggumm21.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

public class ConfigurationXml {

	private Map<String, Element> nameToConfigMap = new TreeMap<String, Element>();

	public static ConfigurationXml getConfigurationXmlInputStream(File file)
			throws FileNotFoundException {

		if (file == null) {
			throw new FileNotFoundException("Given file is null.");
		}
		if (!file.exists()) {
			throw new FileNotFoundException("File '" + file.getAbsolutePath()
					+ "/" + file.getName() + "' not exists.");
		}
		return new ConfigurationXml(new FileInputStream(file));
	}

	/**
	 * Initializes MapReduceXml with the contents of the given input stream,
	 * which should contain the contents of a mapreduce.xml file.
	 */
	public ConfigurationXml(InputStream xmlInputStream) {

		DocumentBuilderFactory docBuilderFactory = createConfigurationDocBuilderFactory();
		try {
			DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
			Document doc = builder.parse(xmlInputStream);
			Element root = doc.getDocumentElement();
			if (!"configurations".equals(root.getTagName())) {
				throw new RuntimeException(
						"Bad configuration list file: top-level element not <configurations>");
			}

			NodeList configurations = root.getChildNodes();

			for (int i = 0; i < configurations.getLength(); i++) {
				if (!(configurations.item(i) instanceof Element)) {
					continue;
				}
				Element configElement = (Element) configurations.item(i);
				String name = configElement.getAttribute("name");
				if (!name.equals("")) {
					nameToConfigMap.put(name, configElement);
				}
			}
		} catch (ParserConfigurationException e) {
			throw new RuntimeException("Couldn't create XML parser.", e);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Couldn't parse mapreduce.xml", e);
		} catch (SAXException e) {
			throw new RuntimeException("Couldn't parse mapreduce.xml", e);
		} catch (IOException e) {
			throw new RuntimeException("Couldn't read mapreduce.xml", e);
		}
	}

	static DocumentBuilderFactory createConfigurationDocBuilderFactory() {

		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
				.newInstance();

		// Mirroring the settings in org.apache.hadoop.conf.Configuration
		docBuilderFactory.setIgnoringComments(true);
		docBuilderFactory.setNamespaceAware(false);
		try {
			docBuilderFactory.setXIncludeAware(true);
		} catch (UnsupportedOperationException e) {
			// Ignore
		}
		return docBuilderFactory;
	}

	public Configuration getConfiguration(String name) {
		Element root = nameToConfigMap.get(name);
		if (root == null) 
			throw new RuntimeException("No configuration for '" + name + "'");
		if (!"configuration".equals(root.getTagName())) {
			throw new RuntimeException(
					"Bad configuration file: top-level element not <configuration>");
		}

		NodeList props = root.getChildNodes();
		
		Configuration conf = new Configuration();
		for (int i = 0; i < props.getLength(); i++) {
			Node propNode = props.item(i);
			if (!(propNode instanceof Element)) {
				continue;
			}
			Element prop = (Element) propNode;
			populateParameterFromProperty(conf, prop);

		}
		
		return conf;

	}

	private void populateParameterFromProperty(Configuration conf, Element prop) {
		
		String name = null;
		String humanName = null;
		String defaultValue = null;

		NodeList fields = prop.getChildNodes();
		for (int j = 0; j < fields.getLength(); j++) {
			Node fieldNode = fields.item(j);
			if (!(fieldNode instanceof Element)) {
				continue;
			}
			Element field = (Element) fieldNode;
			if ("name".equals(field.getTagName())) {
				if (field.hasAttribute("human")) {
					humanName = field.getAttribute("human");
				}
				name = ((Text) field.getFirstChild()).getData().trim();
			}
			if ("value".equals(field.getTagName())) {
				if (field.getFirstChild() != null) {
					defaultValue = field.getFirstChild().getTextContent();
				} else {
					defaultValue = "";
				}
			}
		}

		if (conf.containsKey(name)) {
			ConfigurationEntry entry = conf.get(name);
			entry.addDefaultValue(defaultValue);
		} else {
			conf.put(name, new ConfigurationEntry(name, humanName,
					defaultValue, prop.getAttributes()));
		}
	}
}
